package heap;

import java.io.*;
import global.*;
import chainexception.*;

public class HeapScan {
	private int curDirPID = GlobalConst.INVALID_PAGEID;
	private int curHFPID = GlobalConst.INVALID_PAGEID;
	private HeapFilePage curHFPage = null;
	private Directory curDirPage = null;
	private int curPageDirEntryOffset = 0;
	private boolean bScanComplete = false;
	private RID curRID = null;
	private HeapFile heapfile = null;
	
	public HeapScan(HeapFile heapfile) throws ChainException{
		// Initialize curDirPID the first directory page of the file.
		curDirPID = heapfile.getPageDirectoryPID(); 
		curPageDirEntryOffset = -1;
		curHFPID = GlobalConst.INVALID_PAGEID;
		curHFPage = null;
		bScanComplete = false;
		curRID = null;
		this.heapfile = heapfile;		
		Page page = new Page();
		
		try{
			PageId curDirPageId = new PageId(curDirPID);
			
			Minibase.BufferManager.pinPage(curDirPageId, page, false);
			
			curDirPage = new Directory(page);
		}catch(Exception exception){
			throw new ChainException(exception, "Scan initialization failed.");
		}
	}
	
	public void close() throws ChainException{
		if(curDirPID != GlobalConst.INVALID_PAGEID){
			// Unpin this.
			try{
				Minibase.BufferManager.unpinPage(new PageId(curDirPID), false);
			}catch(Exception exception){
				// Do nothing.
			}
		}
		
		if(curHFPID != GlobalConst.INVALID_PAGEID){
			// Unpin this.
			try{
				Minibase.BufferManager.unpinPage(new PageId(curHFPID), false);
			}catch(Exception exception){
				// Do nothing.
			}
		}
		
		curPageDirEntryOffset = -1;
		curHFPID = GlobalConst.INVALID_PAGEID;
		curHFPage = null;
		bScanComplete = false;
		curRID = null;
	}
	
	public boolean position(RID rid) throws InvalidTupleSizeException, IOException, ChainException{
		if(rid == null || (rid.pageno.pid < 0) || (rid.slotno < 0)){
			throw new InvalidTupleSizeException(null, "Invalid tuple.");
		}
		
		int curPgDirPID = heapfile.getPageDirectoryPID();
		
		// Start search loop.
		while(curPgDirPID != GlobalConst.INVALID_PAGEID){
			// Pin page with PID curPgDirPID.
			Page curPage = new Page();
			PageId curPageId = new PageId(curPgDirPID);
			
			try{
				Minibase.BufferManager.pinPage(curPageId, curPage, false);
			}catch(Exception exception){
				throw new IOException("Unable to pin directory page.");
			}
			
			Directory curDirPage = new Directory(curPage);
			
			for(int pageDirectoryEntryOffset = 0; pageDirectoryEntryOffset < curDirPage.getTotalEntries() - 1;
				pageDirectoryEntryOffset++){
				Entry pageDirectoryEntry = curDirPage.getEntry(pageDirectoryEntryOffset);
				
				if(pageDirectoryEntry.getPID() == rid.pageno.pid){
					// We'll now look inside the HFPage referred to by this PID.
					HeapFilePage hfPage = new HeapFilePage();
					
					try{
						Minibase.BufferManager.pinPage(new PageId(rid.pageno.pid), hfPage, false);
						
						if(hfPage.getRecord(rid) != null){
							// Unpin the curent directory and HF pages
							if(curDirPID != GlobalConst.INVALID_PAGEID){
								Minibase.BufferManager.unpinPage(new PageId(curDirPID), false);
							}
							
							if(curHFPID != GlobalConst.INVALID_PAGEID){
								Minibase.BufferManager.unpinPage(new PageId(curHFPID), false);
							}
							
							// Found the record. Set our private variables.
							curDirPID = curPgDirPID;
							curHFPID = rid.pageno.pid;
							curPageDirEntryOffset = pageDirectoryEntryOffset;
							curRID = new RID(rid.pageno, rid.slotno);
							this.curDirPage = curDirPage;
							curHFPage = hfPage;
							bScanComplete = false;
							
							return true;
						}else{
							// Unpin the directory page before returning.
							Minibase.BufferManager.unpinPage(curPageId, true);
							Minibase.BufferManager.unpinPage(new PageId(rid.pageno.pid), true);	
							
							// Page not found.
							return false;
						}
					}catch(Exception exception){
						throw new IOException("Unable to pin directory page.");
					}
				}
			}
			
			// Next directory.
			curPgDirPID = curDirPage.getNext().getPID();
			
			try{
				Minibase.BufferManager.unpinPage(curPageId, true);
			}catch(Exception exception){
				throw new IOException("Unable to pin directory page.");
			}
		}
		
		// Page not found.
		return false;
	}

	/**
	 * Returns next tuple in the scan.
	 * @param rid
	 * @return
	 * @throws InvalidTupleSizeException
	 * @throws IOException
	 */
	public Tuple getNext(RID rid) throws InvalidTupleSizeException, IOException{
		if(rid == null){
			throw new InvalidTupleSizeException(null, "Invalid tuple.");
		}
		
		// Keep looking till the first non-null HFPage is encountered.
		while(curHFPage == null || curHFPage.empty() || (curRID != null && curHFPage.nextRecord(curRID) == null)){
			try {
				fetchNextHFPage();

				curRID = null;
			} catch (ChainException e) {
				bScanComplete = true;

				return null;
			}

			if(bScanComplete){
				return null;
			}
		}

		Tuple nextTuple = null;

		// Return the next record from the curent HFPage
		if(curRID == null){
			curRID = curHFPage.firstRecord();
		}else{
			curRID = curHFPage.nextRecord(curRID);
		}

		try{ 
			nextTuple = curHFPage.getRecord(curRID);
			rid.pageno = new PageId(curRID.pageno.pid);
			rid.slotno = curRID.slotno;			
		}catch(Exception exception){
			throw new InvalidTupleSizeException(exception, "Unable to get record.");
		}

		return nextTuple;
	}
	
	/**
	 * Fetches the next page into curHFPage. Pins it too.
	 * Before pinning the next page, the curent one will be unpinned.
	 */
	private void fetchNextHFPage() throws ChainException{
		if(curHFPID != GlobalConst.INVALID_PAGEID){
			PageId curHFPageId = new PageId(curHFPID);
			
			try{
				Minibase.BufferManager.unpinPage(curHFPageId, false);
			}catch(Exception exception){
				throw new ChainException(exception, "Unable to unpin HFPage.");
			}
		}
		
		// Check if we have reached the end of this directory.
		if(curPageDirEntryOffset >= curDirPage.getTotalEntries() - 1){
			// End of directory reached. Get next directory, if available.
			// Also, reset curPageDirEntryOffset.
			int nextDirPagePID = curDirPage.getNext().getPID();
			
			// Unpin curent directory page and pin the next one.
			Minibase.BufferManager.unpinPage(new PageId(curDirPID), false);
			
			// If nextDirPagePID is invalid, we've reached the end of the scan.
			// Reset all private tracking variables to invalid and exit.
			if(nextDirPagePID == GlobalConst.INVALID_PAGEID){
				curDirPID = GlobalConst.INVALID_PAGEID;
				curHFPID = GlobalConst.INVALID_PAGEID;
				curHFPage = null;
				curDirPage = null;
				curPageDirEntryOffset = -1;
				bScanComplete = true;
				
				return;
			}
			
			curDirPID = nextDirPagePID;
			Page page = new Page();
			
			try{
				Minibase.BufferManager.pinPage(new PageId(curDirPID), page, false);
			}catch(Exception exception){
				throw new ChainException(exception, "Unable to pin directory page.");
			}
			
			curDirPage = new Directory(page);
			// Set curent directory entry offset to -1 to indicate
			// that scanning hasn't begun yet on this directory.
			curPageDirEntryOffset = -1;			
		}
		
		curPageDirEntryOffset++;
		
		Entry pageDirectoryEntry = curDirPage.getEntry(curPageDirEntryOffset);
		// Fetch the HFPage in.
		curHFPID = pageDirectoryEntry.getPID();
		
		if(curHFPID != GlobalConst.INVALID_PAGEID){
			// Set curent HFPage.
			Page page = new Page();

			try{
				Minibase.BufferManager.pinPage(new PageId(curHFPID), page, false);
			}catch(Exception exception){
				throw new ChainException(exception, "Unable to pin HFPage.");
			}

			curHFPage = new HeapFilePage(page);
		}else{
			curHFPage = null;
		}
	}
}
